'''

    Models for the SupaCrypt


'''

import re   # used for names validations

from django.db import models
from django.contrib.auth.models import User

from the_crypt._HistoryfiedModel import Event, Historyfied

class Status(models.Model):
    name = models.CharField(max_length=32)
    after = models.ForeignKey('self', null=True, blank=True)
    
    def __unicode__(self):
        return self.name


@Historyfied.BindEvents        
class Tracked(Historyfied):
    '''
    This Model is herited to add specific Tracked fields.
    Use the Cast and As* methods to cast your object to the desired type.
    
    As a Django restriction, every relational fields in subclasses
    will have to give the 'related_name' keyword argument.
    
    '''
    # Possible values for the type field.
    # This field is used to cast a Tracked instance to the
    # actual Model class.
    TYPES = (
        # DBValue,   DisplayValue
        ('Variant',  'Variant'),
        ('Project',  'Project'),
        ('Lib',      'Library'),
        ('Entity',   'Entity'),
        ('Sequence', 'Sequence'),
        ('Shot',     'Shot'),
    )
    type = models.CharField(max_length=32, choices=TYPES, blank=True) # Class name of this object.
    depend = models.ForeignKey('self', null=True, blank=True)
    priority = models.PositiveSmallIntegerField(default=50,blank=True)
    start_date = models.DateTimeField(null=True,blank=True)
    due_date = models.DateTimeField(null=True,blank=True)
    status = models.ForeignKey(Status)
    
    def __unicode__(self):
        return self.type+': '+self.name
    
    def As(self, type):
        '''
        Use As(modelName) to cast a Tracked object to its final type:
            project = tracked.As('Project')
        
        Returns None if the tracked object is not of the given type.
        '''
        if self.type != type:
            return None
        return getattr(self,type.lower())
    
    @classmethod
    def _pre_save(klass, sender, instance, using, **kwargs):
        '''
        Call the base class _pre_save as required by the Historyfied
        ancestor class and set the type field value if needed.
        '''
        super(Tracked, klass)._pre_save(sender, instance, using, **kwargs)
        print "########## Tracked "+klass.__name__+" PRE-SAVE for type field"
        if not instance.type:
            print '   using', `klass.__name__`
            instance.type=klass.__name__

@Historyfied.BindEvents        
class Variant(Tracked):
    '''
    A Tracked can have a list variants representing alternatives/parallel objects.
    '''
    description = models.TextField()
    varying = models.ForeignKey(Tracked, related_name='varying')
    
@Historyfied.BindEvents
class Post(Historyfied):
    '''
    Posts can be related to a Tracked object and optionnally have a parent Post.
    This forms a threaded forum with Tracked topic.
    '''
    tracked = models.ForeignKey(Tracked, blank=True)
    parent = models.ForeignKey('self', blank=True)
    subject = models.CharField(max_length=64)
    content = models.TextField()
    subscribers = models.ManyToManyField(User, blank=True)
    
    def __unicode__(self):
        return self.name


@Historyfied.BindEvents
class Project(Tracked):
    label = models.CharField(max_length=64)
    description = models.TextField()
    
    def __unicode__(self):
        return self.name


@Historyfied.BindEvents
class Lib(Tracked):
    project = models.ForeignKey(Project, related_name='libs')
    description = models.TextField()


@Historyfied.BindEvents
class Entity(Tracked):
    '''
    Project Entities can be typed with As* functions.
    '''
    TYPES = (
        ('CHAR','CHAR'),
        ('SET','SET'),
        ('PROP','PROP'),
    )
    ent_type = models.CharField(max_length=16, choices=TYPES) # Class name of this Entity
    lib = models.ForeignKey(Lib, related_name='entities')
    
    hasHair = models.BooleanField()
    hasCloth = models.BooleanField()
    
    def __unicode__(self):
        return '_'.join((self.lib.name,self.ent_type or 'other',self.name))
    
    def CastEnt(self):
        return getattr(self,self.ent_type.lower())
        
    def AsEnt(self, type):
        if self.ent_type != type:
            return None
        return getattr(self,type.lower())
        
    def AsChar(self):
        return self.AsEnt('Char')
    
    def AsSet(self):
        return self.AsEnt('Set')
    
    def AsProp(self):
        return self.AsEnt('Prop')
    
    @classmethod
    def _pre_save(klass, sender, instance, using, **kwargs):
        '''
        Call the base class _pre_save as required by the Historyfied
        ancestor class and set the ent_type field value if needed.
        '''
        super(Entity, klass)._pre_save(sender, instance, using, **kwargs)
        print "########## Entity "+klass.__name__+" PRE-SAVE for ent_type and project fields"
        if not instance.ent_type:
            print '   using ent_type', `klass.__name__`
            instance.ent_type=klass.__name__


@Historyfied.BindEvents
class Char(Entity):
    FAMILIES = (
        ('Main','Main'),
        ('Secondary','Secondary'),
        ('Tertiary','Tertiary'),
        ('Crowd','Crowd'),
    )
    family = models.CharField(
        max_length=32,
        choices=FAMILIES,
        help_text='Use the family to classify Characters.'
    )


@Historyfied.BindEvents
class Set(Entity):
    location = models.CharField(
        max_length=32,
        help_text='The location is used to group some related/connected Sets'
    )


@Historyfied.BindEvents
class Prop(Entity):
    group = models.CharField(
        max_length=32,
        help_text='You may use "/" in groups to create nested groups.'
    )

@Historyfied.BindEvents
class Sequence(Tracked):
    project = models.ForeignKey(Project, related_name='sequences')
    order = models.PositiveSmallIntegerField(default=0)

@Historyfied.BindEvents
class Shot(Tracked):
    sequence = models.ForeignKey(Sequence, related_name='shots')
    order = models.PositiveSmallIntegerField(default=0)

